﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace ESpace
{
    [ToolboxBitmap(typeof(EListBox), "EListBox.bmp")]
    [DefaultEvent("SelectedItemChanged")]
    public partial class EListBox : UserControl
    {
        public class Item
        {
            public bool Checked = false;
            public String Str;
            public int Tag; // тэг, для пользования
            public Brush brText; // цвет теста
            public Font Font; // шрифт текста

            public Item(String Str)
            {
                this.Str = Str;
                brText = new SolidBrush(Color.Black);
                Font = new Font("Microsoft Sans Serif", 8.25f);
            }

            public Item(String Str, bool Checked)
            {
                this.Str = Str;
                this.Checked = Checked;
                brText = new SolidBrush(Color.Black);
                Font = new Font("Microsoft Sans Serif", 8.25f);
            }

            public Item(String Str, bool Checked, Brush TextBrush)
            {
                this.Str = Str;
                this.Checked = Checked;
                brText = TextBrush;
                Font = new Font("Microsoft Sans Serif", 8.25f);
            }

            public Item(String Str, bool Checked, Brush TextBrush, Font TextFont)
            {
                this.Str = Str;
                this.Checked = Checked;
                brText = TextBrush;
                Font = TextFont;
            }

        }
        
        public List<Item> Itm = new List<Item>();

        #region *** Внутренние переменные ************************************
        
        Graphics gr;
        //int shYStr = 1; // смещение отрисовки строки, чтобы она рисовалась посередине итема 
        Item DownItm = null;
        int downInd = -1;
        int moveInd = -1;
        int pDownY = -1; // положение мыши по y во время нажатия
        int pMoveY; // положение мыши по y во время движения
        bool bMoveItem;

        Pen pnSel = new Pen(Color.Black);
        /// <summary>Оконтовка выделения</summary>
        //public Pen PnSel { get { return pnSel; } set { pnSel = value; DrawAll(); } }

        Brush brSel = new SolidBrush(Color.FromArgb(230, 230, 255));
        Brush brMove = new SolidBrush(Color.FromArgb(100, 230, 230, 255));
        /// <summary>Цвет фона выделения</summary>
        //public Brush BrSel { get { return brSel; } set { brSel = value; DrawAll(); } }

        #endregion *** Внутренние переменные ************************************

        #region *** свойства ************************************

        int actI = -1;
        [DefaultValue(-1)]
        /// <summary>Индекс выделенной строки</summary>
        public int SelectedIndex
        {
            get { return actI; }
            set
            {
                if (value < -1 || value >= Itm.Count)
                {
                    throw new Exception("Ошибка: Индекс вне границ массива.");
                }
                // если переход на ту же строку, выходим
                if (value == actI) return;

                // устанавливаем новую строку
                actI = value;

                // вызываем событие смены выделения
                OnSelectedItemChanged();

                DrawAll();
            }
        }

        bool checkMode = false;
        [DefaultValue(false)]
        /// <summary>Режим выбора полей</summary>
        public bool CheckMode
        {
            get { return checkMode; }
            set { checkMode = value; DrawAll(); }
        }

        bool mCheck = true;
        [DefaultValue(true)]
        /// <summary>Можно выбрать сразу несколько полей</summary>
        public bool MultCheck
        {
            get { return mCheck; }
            set { mCheck = value; }
        }

        bool readOnly = false;
        [DefaultValue(false)]
        /// <summary>Только чтение (выделение и выбор пользователем отключены)</summary>
        public bool ReadOnly
        {
            get { return readOnly; }
            set { readOnly = value; DrawAll(); }
        }

        int stepY = 15;
        [DefaultValue(15)]
        /// <summary>Высота строки</summary>
        public int StepY
        {
            get { return stepY; }
            set
            {
                stepY = value;
                ResizeIm();
                DrawAll();
            }
        }

        /// <summary>Количество строк</summary>
        public int Count { get { return Itm.Count; } }

        bool moveItems = true;
        [DefaultValue(true)]
        public bool MoveItems { get { return moveItems; } set { moveItems = value; } }

        Color bkColor = Color.White;
        public Color BkColor { get { return bkColor; } set { bkColor = value; } }

        #endregion *** свойства ************************************

        public EListBox()
        {
            InitializeComponent();
            im.Image = new Bitmap(im.Width, im.Height);
            gr = Graphics.FromImage(im.Image);
            ResizeIm();
            DrawAll();
            //im.MouseWheel += new MouseEventHandler(im_MouseWheel);
        }

        #region *** События ************************************

        public delegate void SelectedItemChangedDelegate(object Sender);
        /// <summary>Изменилась выделенная строка</summary>
        public event SelectedItemChangedDelegate SelectedItemChanged;
        public void OnSelectedItemChanged()
        {
            if (SelectedItemChanged != null)
                SelectedItemChanged(this);
        }

        public delegate void CheckChangedDelegate(object Sender, int ind);
        /// <summary>Изменился выбор строки</summary>
        public event CheckChangedDelegate CheckChanged;
        public void OnCheckChanged(int ind)
        {
            if (CheckChanged != null)
                CheckChanged(this, ind);
        }

        public delegate void MoveItemDelegate(object Sender, int oldInd, int newInd);
        /// <summary>Была перемещена строка</summary>
        public event MoveItemDelegate MoveItem;
        public void OnMoveItem(int oldInd, int newInd)
        {
            if (MoveItem != null)
                MoveItem(this, oldInd, newInd);
        }

        #endregion *** События ************************************

        #region *** методы ********************************************

        public void Refill(String[] strL)
        {
            Clear();
            actI = -1;
            for (int i = 0; i < strL.Length;i++ )
                Itm.Add(new Item(strL[i], false));
            ResizeIm();
            DrawAll();
        }

        public void Refill(List<Item> itmL)
        {
            actI = -1;
            Itm = itmL;
            ResizeIm();
            DrawAll();
        }

        override public void Refresh()
        {
            ResizeIm();
            DrawAll();
            base.Refresh();
        }

        /// <summary>
        /// Добавление строки
        /// </summary>
        /// <param name="s">Строка</param>
        /// <param name="Checked">Строка выбрана</param>
        public void Add(String s, bool Checked)
        {
            Itm.Add(new Item(s, Checked));
            ResizeIm();
            DrawAll();
        }

        /// <summary>
        /// Добавление строки
        /// </summary>
        /// <param name="s">Строка</param>
        public void Add(String s) { Add(s, false); }

        /// <summary>
        /// Удаление строки
        /// </summary>
        /// <param name="ind">Индекс строки</param>
        public void RemoveAt(int ind)
        {
            Itm.RemoveAt(ind);
            if (actI == ind) actI = -1;
            else if (actI > ind) actI--;
            ResizeIm();
            DrawAll();
        }

        /// <summary>
        /// Вставка строки перед строкой с индексом ind 
        /// (если индекс >= количеству строка, новая строка вставится после всех)
        /// </summary>
        /// <param name="ind">индекс строки, перед которой вставляется новая строка</param>
        /// <param name="s">новая строка</param>
        /// <param name="Checked">новая строка выбрана</param>
        public void InsertBefor(int ind, String s, bool Checked)
        {
            Itm.Insert(ind, new Item(s, Checked));
            if (actI >= ind) actI++;
            ResizeIm();
            DrawAll();
        }

        /// <summary> Очистить лист </summary>
        public void Clear()
        {
            Itm.Clear();
            actI = -1;
            ResizeIm();
            DrawAll();
        }

        /// <summary>
        /// Установить/снять выбор
        /// </summary>
        /// <param name="ind">номер строки</param>
        /// <param name="Checked">строка выбрана</param>
        public void SetCheck(int ind, bool Checked)
        {
            if (Itm[ind].Checked == Checked) return;
            Itm[ind].Checked = Checked;
            // если допустимо только одно выделение, снимаем выделение с остальных
            if (Checked == true && !mCheck)
            {
                for (int i = 0; i < Itm.Count; i++)
                    if (i != ind && Itm[i].Checked) Itm[i].Checked = false;
            }
            DrawAll();
            OnCheckChanged(ind);
        }

        public void ScrollToSelect()
        { 
            if(!VerticalScroll.Visible)return;
            if(SelectedIndex==-1)return;
            int posY = SelectedIndex*stepY;
            int y1 = posY-VerticalScroll.Value;
            int y2 = (posY + stepY)-VerticalScroll.Value;
            if(y1>=0 && y2<ClientRectangle.Height)return;

            int n = VerticalScroll.Value;
            if(y1<0)n = n- (ClientRectangle.Height/2 - y1);
            if(y2>=ClientRectangle.Height)n= n + (y2-ClientRectangle.Height/2);
            if (n < 0) n = 0; if (n > VerticalScroll.Maximum) n = VerticalScroll.Maximum;
            base.AutoScrollPosition = new Point(0, n);
        }

        #endregion *** методы ********************************************

        void DrawAll()
        {
            gr.Clear(bkColor);

            // строки
            for (int i = 0; i < Itm.Count; i++)
            {
                int y = i * stepY;
                int shYStr = (int)((stepY - gr.MeasureString("рЦЩЙ", Itm[i].Font).Height) / 2);
                // если есть переносимая строка
                if (bMoveItem)
                {
                    // если строка сдвинулась, смещение остальных строк, для образования пустого места
                    if (downInd != moveInd)
                    {
                        // если вверх
                        if (downInd > moveInd && moveInd <= i && i <= downInd)
                            y += stepY;
                        // если вниз
                        if (downInd < moveInd && moveInd >= i && i >= downInd)
                            y -= stepY;
                    }

                    // оставляем для выделеной строки пустое место
                    if (i == downInd) continue;
                }


                // если строка - это выделенная строка
                if (i == actI && !readOnly)
                {
                    gr.FillRectangle(brSel, 0, y, im.Width, stepY);
                    gr.DrawRectangle(pnSel, -1, y, im.Width + 1, stepY);
                }

                // текст строки
                if (checkMode)
                {
                    gr.FillRectangle(Brushes.White, 2, y + 2, stepY - 4, stepY - 4);
                    gr.DrawRectangle(Pens.Black, 2, y + 2, stepY - 4, stepY - 4);
                    if (Itm[i].Checked)
                    {
                        gr.DrawLine(Pens.Blue, 4, y + 6, stepY / 3, y + stepY - 5);
                        gr.DrawLine(Pens.Blue, stepY / 3, y + stepY - 5, stepY - 5, y + 4);
                    }
                    gr.DrawString(Itm[i].Str, Itm[i].Font, Itm[i].brText, stepY, y + shYStr);
                }
                else
                    gr.DrawString(Itm[i].Str, Itm[i].Font, Itm[i].brText, 0, y + shYStr);
            }

            // переносимая строка
            if (bMoveItem)
            {
                int shYStr = (int)((stepY - gr.MeasureString("рЦЩЙ", DownItm.Font).Height) / 2);
                int y = (pDownY / stepY) * stepY + pMoveY - pDownY;
                if (checkMode)
                {
                    gr.FillRectangle(brMove, stepY, y, im.Width, stepY);
                    gr.DrawString(DownItm.Str, DownItm.Font, DownItm.brText, stepY, y + shYStr);
                }
                else
                {
                    gr.FillRectangle(brMove, 0, y, im.Width, stepY);
                    gr.DrawString(DownItm.Str, DownItm.Font, DownItm.brText, 0, y + shYStr);
                }
            }


            im.Refresh();
        }
        
        void ResizeIm()
        {
            if (Width>0 && im.Width > 0 && im.Height > 0)
            {
                int h = Itm.Count * stepY + 1;
                im.Height = h;
                im.Image = new Bitmap(im.Width, h);
                gr = Graphics.FromImage(im.Image);
            }
        }

        #region *** Собития компонентов ********************************************
        bool bImMouseDown = false;
        private void im_MouseDown(object sender, MouseEventArgs e)
        {
            if (readOnly)
            {
                bImMouseDown = true;
                this.OnMouseDown(e);
                bImMouseDown = false;
                return;
            }
            int ind = e.Y / stepY;
            if (ind < 0 || ind >= Itm.Count)
            {
                SelectedIndex = -1;
                bImMouseDown = true;
                this.OnMouseDown(e);
                bImMouseDown = false;
                return;
            }

            // если выбор галочки
            if (checkMode && e.X < stepY)
            {
                SetCheck(ind, !Itm[ind].Checked);
                bImMouseDown = true;
                this.OnMouseDown(e);
                bImMouseDown = false;
                return;
            }

            // присваиваем
            SelectedIndex = ind;

            // для перемещения
            DownItm = Itm[actI];
            downInd = ind;
            moveInd = downInd;
            pDownY = e.Y;

            bImMouseDown = true;
            this.OnMouseDown(e);
            bImMouseDown = false;
        }

       /* private void im_MouseWheel(object sender, MouseEventArgs e)
        { 
            
        }*/

        private void im_MouseMove(object sender, MouseEventArgs e)
        {
            this.OnMouseMove(e);
            pMoveY = e.Y;
            if (pDownY!=-1 && moveItems && Math.Abs(pMoveY - pDownY) > 1)
            {
                bMoveItem = true;
                moveInd = ((pDownY / stepY) * stepY + pMoveY - pDownY + stepY / 2) / stepY;
                if (moveInd < 0) moveInd = 0;
                if (moveInd >= Itm.Count) moveInd = Itm.Count - 1;
                DrawAll();
            }
        }

        private void im_MouseUp(object sender, MouseEventArgs e)
        {
            this.OnMouseUp(e);
            if (bMoveItem)
            {
                if (downInd != moveInd)
                {
                    Itm.RemoveAt(downInd);
                    Itm.Insert(moveInd, DownItm);
                    actI = moveInd; // при перемещении не вызываем событие смены. Просто меняем индекс
                    OnMoveItem(downInd, moveInd);
                }

                DrawAll();
            }

            DownItm = null;

            pDownY = -1;

            if (bMoveItem)
            {
                bMoveItem = false;
                DrawAll();
            }
            this.Focus();
        }

        private void im_MouseLeave(object sender, EventArgs e)
        {
            this.OnMouseLeave(e);
        }

        private void EListBox_Resize(object sender, EventArgs e)
        {
            ResizeIm();
            DrawAll();
        }

        private void EListBox_MouseDown(object sender, MouseEventArgs e)
        {
            if (!bImMouseDown && !readOnly) SelectedIndex = -1;
        }

        #endregion *** Собития компонентов ********************************************

        private void im_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.OnMouseDoubleClick(e);
        }

        private void im_DoubleClick(object sender, EventArgs e)
        {
            this.OnDoubleClick(e);
        }

    }

}
